using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Transactions;
namespace Next.Whs.MQ {
	public class QueueManager {
		public static readonly int DefaultGetTimeout=20;
		///////////////////////////////////////////////////////////////////////////
		// enumerations
		public enum OpenOptions {
			Browse=(int)(Constants.MQOO_BROWSE|Constants.MQOO_FAIL_IF_QUIESCING),
			Write=(int)(Constants.MQOO_OUTPUT|Constants.MQOO_FAIL_IF_QUIESCING),
			ReadWrite=(int)(Constants.MQOO_INPUT_EXCLUSIVE|Constants.MQOO_OUTPUT|Constants.MQOO_FAIL_IF_QUIESCING),
			TransactionalReadWrite=(int)(Constants.MQOO_INPUT_EXCLUSIVE|Constants.MQOO_OUTPUT|Constants.MQOO_FAIL_IF_QUIESCING|Constants.MQOO_INQUIRE)
		}
		///////////////////////////////////////////////////////////////////////////
		// MQ API functions
		[DllImport("mqic.dll")]
		static extern void MQCONNX([In,Out] MQNAME queueName,[In,Out] MQCNO options,ref uint connectionHandle,ref uint completionCode,ref uint reasonCode);
		[DllImport("mqic.dll")]
		static extern void MQDISC(ref uint connectionHandle,ref uint completionCode,ref uint reasonCode);
		[DllImport("mqic.dll")]
		static extern void MQOPEN(uint connectionHandle,[In,Out] MQOD objectDescriptor,uint options,ref uint handle,ref uint completionCode,ref uint reasonCode);
		[DllImport("mqic.dll")]
		static extern void MQBACK(uint connectionHandle,ref uint completionCode,ref uint reasonCode);
		///////////////////////////////////////////////////////////////////////////
		// attributes
		uint _connectionHandle=0;
		uint _completionCode=0;
		uint _reasonCode=0;
		string _lastError="";
		///////////////////////////////////////////////////////////////////////////
		// LastError property
		public string LastError {
			get {
				return _lastError;
			}
		}
		///////////////////////////////////////////////////////////////////////////
		// connect to queue manager
		public bool Connect(string queueManager,string channel,string host) {
			if(_connectionHandle!=0)
			{
				_lastError="already connected to a queue manager";
				return false;
			}
			bool result=true;
			MQNAME qmName=new MQNAME(queueManager);
			MQCD channelDef=new MQCD();
			channelDef._ChannelName=MQUtils.StringToByteArray(channel,Constants.MQ_CHANNEL_NAME_LENGTH);
			channelDef._ConnectionName=MQUtils.StringToByteArray(host,Constants.MQ_CONN_NAME_LENGTH);
			MQCNO connectOptions=new MQCNO();
			connectOptions._Version=Constants.MQCNO_VERSION_2;
			// marshal MQCD into MQCNO field	
			connectOptions._ClientConnPtr=Marshal.AllocCoTaskMem(Marshal.SizeOf(channelDef));
			Marshal.StructureToPtr(channelDef,connectOptions._ClientConnPtr,false);
			// call MQCONNX function
			MQCONNX(qmName,connectOptions,ref _connectionHandle,ref _completionCode,ref _reasonCode);
			// release buffer used for marshaling data
			Marshal.FreeCoTaskMem(connectOptions._ClientConnPtr);
			// check return code
			if(_completionCode==Constants.MQCC_FAILED)
			{
				result=false;
				_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
			}
			return result;
		}
		///////////////////////////////////////////////////////////////////////////
		// disconnect from queue manager
		public bool Disconnect() {
			if(_connectionHandle==0)
			{
				_lastError="not connected to a queue manager";
				return false;
			}
			bool result=true;
			MQDISC(ref _connectionHandle,ref _completionCode,ref _reasonCode);
			if(_reasonCode!=ReasonCodes.MQRC_NONE)
			{
				_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
				result=false;
			}
			_connectionHandle=0;
			return result;
		}
		///////////////////////////////////////////////////////////////////////////
		// open a queue
		public Queue OpenQueue(string name,OpenOptions options) {
			if(_connectionHandle==0)
			{
				_lastError="not connected to a queue manager";
				return null;
			}
			Queue result=null;
			uint mqoptions=(uint)options;
			uint handle=0;
			MQOD objectDescriptor=new MQOD();
			objectDescriptor._ObjectName=MQUtils.StringToByteArray(name,Constants.MQ_Q_NAME_LENGTH);
			MQOPEN(_connectionHandle,objectDescriptor,mqoptions,ref handle,ref _completionCode,ref _reasonCode);
			if(_reasonCode!=ReasonCodes.MQRC_NONE)
			{
				_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
			}
			else
			{
				result=new Queue(_connectionHandle,handle);
			}
			return result;
		}
		///////////////////////////////////////////////////////////////////////////
		// open a remote queue
		public Queue OpenRemoteQueue(string name,string transmissionQueueName,OpenOptions options) {
			if(_connectionHandle==0)
			{
				_lastError="not connected to a queue manager";
				return null;
			}
			Queue result=null;
			uint mqoptions=(uint)options;
			uint handle=0;
			MQOD objectDescriptor=new MQOD();
			objectDescriptor._ObjectName=MQUtils.StringToByteArray(name,Constants.MQ_Q_NAME_LENGTH);
			objectDescriptor._ObjectQMgrName=MQUtils.StringToByteArray(transmissionQueueName,Constants.MQ_Q_MGR_NAME_LENGTH);
			MQOPEN(_connectionHandle,objectDescriptor,mqoptions,ref handle,ref _completionCode,ref _reasonCode);
			if(_reasonCode!=ReasonCodes.MQRC_NONE)
			{
				_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
			}
			else
			{
				result=new Queue(_connectionHandle,handle);
			}
			return result;
		}
		public bool TransactionalGetMessage(string queueManager,string channel,string host,string queue,Next.Whs.Loader.Interfaces.IRecordProcessor processor,int retryAttempts) {
			bool result=false;
			bool connected=true;
			string message="";
			int retry=1;
			try
			{
				if(_connectionHandle!=0)
				{
					connected=false;
					_lastError="already connected to a queue manager";
				}
				if(connected)
				{
					MQNAME qmName=new MQNAME(queueManager);
					MQCD channelDef=new MQCD();
					channelDef._ChannelName=MQUtils.StringToByteArray(channel,Constants.MQ_CHANNEL_NAME_LENGTH);
					channelDef._ConnectionName=MQUtils.StringToByteArray(host,Constants.MQ_CONN_NAME_LENGTH);
					MQCNO connectOptions=new MQCNO();
					connectOptions._Version=Constants.MQCNO_VERSION_2;
					// marshal MQCD into MQCNO field	
					connectOptions._ClientConnPtr=Marshal.AllocCoTaskMem(Marshal.SizeOf(channelDef));
					Marshal.StructureToPtr(channelDef,connectOptions._ClientConnPtr,false);
					// call MQCONNX function
					MQCONNX(qmName,connectOptions,ref _connectionHandle,ref _completionCode,ref _reasonCode);
					// release buffer used for marshaling data
					Marshal.FreeCoTaskMem(connectOptions._ClientConnPtr);
					// check return code
					if(_completionCode==Constants.MQCC_FAILED)
					{
						_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
						connected=false;
					}
					if(!connected)
					{
						//failed to connect to queue manager
						return false;
					}
					Queue q=null;
					q=this.OpenQueue(queue,OpenOptions.TransactionalReadWrite);
					if(q!=null)
					{
						message=q.Get(92160,DefaultGetTimeout,true);
						while(message!=null)
						{
							using(TransactionScope trans=new TransactionScope(TransactionScopeOption.Required))
							{
								try
								{
									processor.Process(message);
									trans.Complete();
									result=true;
									message=q.Get(92160,DefaultGetTimeout,true);
								}
								catch
								{
									if(retry<retryAttempts)
									{
										MQBACK(_connectionHandle,ref _completionCode,ref _reasonCode);
										if(_reasonCode!=ReasonCodes.MQRC_NONE)
										{
											_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
											result=false;
										}
										retry++;
									}
									else
									{
										//put on reject q
										Queue rejectQueue=this.OpenQueue(string.Concat(queue,".REJECT"),Next.Whs.MQ.QueueManager.OpenOptions.ReadWrite);
										if(rejectQueue!=null)
										{
											rejectQueue.Put(message);
											rejectQueue.Close();
										}
										else
											EventLog.WriteEntry("Next.Whs.Loader.Framework",string.Format("Failed to connect to queue {0} hosted by queue manager {1} on {2} using channel {3} to write message {4}",string.Concat(queue,".REJECT"),queue,host,channel,message),EventLogEntryType.Information);
										trans.Complete();
										retry=0;
									}
									message=null;
									message=q.Get(92160,DefaultGetTimeout,true);
								}
							}
						}
					}
				}
			}
			finally
			{
				if(connected)
					Disconnect();
			}
			return result;
		}
	}
	public class Queue {
		[DllImport("mqic.dll")]
		static extern void MQCLOSE(uint connectionHandle,ref uint handle,uint options,ref uint completionCode,ref uint reasonCode);
		[DllImport("mqic.dll")]
		static extern void MQPUT(uint connectionHandle,uint handle,[In,Out] MQMD messageDescriptor,[In,Out] MQPMO putOptions,uint length,[MarshalAs(UnmanagedType.LPStr)] string message,ref uint completionCode,ref uint reasonCode);
		[DllImport("mqic.dll")]
		static extern void MQGET(uint connectionHandle,uint handle,[In,Out] MQMD messageDescriptor,[In,Out] MQGMO getOptions,uint length,byte[] message,ref uint dataLength,ref uint completionCode,ref uint reasonCode);
		[DllImport("mqic.dll")]
		static extern void MQINQ(uint connectionHandle,uint handle,[In,Out] MQMD messageDescriptor,[In,Out] MQGMO getOptions,uint length,byte[] message,ref uint dataLength,ref uint completionCode,ref uint reasonCode);
		///////////////////////////////////////////////////////////////////////////
		// attributes
		uint _connectionHandle=0;
		uint _handle=0;
		uint _completionCode=0;
		uint _reasonCode=0;
		string _lastError="";
		///////////////////////////////////////////////////////////////////////////
		// constructor(s)
		private Queue() {
		}
		public Queue(uint connectionHandle,uint handle) {
			_connectionHandle=connectionHandle;
			_handle=handle;
		}
		///////////////////////////////////////////////////////////////////////////
		// LastError property
		public string LastError {
			get {
				return _lastError;
			}
		}
		///////////////////////////////////////////////////////////////////////////
		// close the queue
		public bool Close() {
			if(_handle==0)
			{
				_lastError="queue is not open";
				return false;
			}
			bool result=true;
			uint mqoptions=0;
			MQCLOSE(_connectionHandle,ref _handle,mqoptions,ref _completionCode,ref _reasonCode);
			if(_reasonCode!=ReasonCodes.MQRC_NONE)
			{
				_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
				result=false;
			}
			_handle=0;
			return result;
		}
		///////////////////////////////////////////////////////////////////////////
		// put a message on the queue
		public bool Put(string message) {
			if(_handle==0)
			{
				_lastError="queue is not open";
				return false;
			}
			bool result=true;
			MQMD messageDescriptor=new MQMD();
			MQPMO putOptions=new MQPMO();
			messageDescriptor._Format=MQUtils.StringToByteArray("MQSTR   ",8);
			messageDescriptor._Encoding=Constants.MQENC_NATIVE;
			messageDescriptor._CodedCharSetId=Constants.MQCCSI_ISO8859;
			uint messageLength=(uint)message.Length;
			MQPUT(_connectionHandle,_handle,messageDescriptor,putOptions,messageLength,message,ref _completionCode,ref _reasonCode);
			if(_reasonCode!=ReasonCodes.MQRC_NONE)
			{
				_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
				result=false;
			}
			string test=new string('\0',256);
			return result;
		}
		///////////////////////////////////////////////////////////////////////////
		// get a single message from the queue
		public string Get(int length) {
			return this.Get(length,QueueManager.DefaultGetTimeout);
		}
		///////////////////////////////////////////////////////////////////////////
		// get a single message from the queue
		public string Get(int length,int timeout) {
			if(_handle==0)
			{
				_lastError="queue is not open";
				return null;
			}
			//initialise descriptor
			MQMD messageDescriptor=SetUpMessageDescriptor();
			// intialise get options
			MQGMO getOptions=SetUpGetOptions(timeout);
			uint messageLength=(uint)length;
			uint mqLength=0;
			string result=null;
			byte[] message=new Byte[length];


			MQINQ(_connectionHandle,0x00000020,messageDescriptor,getOptions,messageLength,message,ref mqLength,ref _completionCode,ref _reasonCode);

			MQGET(_connectionHandle,_handle,messageDescriptor,getOptions,messageLength,message,ref mqLength,ref _completionCode,ref _reasonCode);
			if(_reasonCode!=ReasonCodes.MQRC_NONE)
			{
				_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
			}
			else
			{
				result=GetText(message,(int)mqLength);
			}
			return result;
		}
		///////////////////////////////////////////////////////////////////////////
		// get a single message from the queue
		public string Get(int length,int timeout, bool transactionalGet) {
			if(_handle==0)
			{
				_lastError="queue is not open";
				return null;
			}
			//initialise descriptor
			MQMD messageDescriptor=SetUpMessageDescriptor();
			// intialise get options
			MQGMO getOptions=null;
			if(transactionalGet)
				getOptions=SetUpTransactionalGetOptions(timeout);
			else
				getOptions=SetUpGetOptions(timeout);
			uint messageLength=(uint)length;
			uint mqLength=0;
			string result=null;
			byte[] message=new Byte[length];
			MQGET(_connectionHandle,_handle,messageDescriptor,getOptions,messageLength,message,ref mqLength,ref _completionCode,ref _reasonCode);
			if(_reasonCode!=ReasonCodes.MQRC_NONE)
			{
				_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
			}
			else
			{
				result=GetText(message,(int)mqLength);
			}
			return result;
		}
		///////////////////////////////////////////////////////////////////////////
		// get a single message from the queue ACCORDING TO MESSAGE ID
		public bool GetMessageAndID(int length,ref string messageString,ref byte[] messageID) {
			return this.GetMessageAndID(length,QueueManager.DefaultGetTimeout,ref messageString,ref messageID);
		}
		///////////////////////////////////////////////////////////////////////////
		// get a single message from the queue ACCORDING TO MESSAGE ID
		public bool GetMessageAndID(int length,int timeout,ref string messageString,ref byte[] messageID) {
			if(_handle==0)
			{
				_lastError="queue is not open";
				return false;
			}
			//initialise descriptor
			MQMD messageDescriptor=SetUpMessageDescriptor();
			// intialise get options
			MQGMO getOptions=SetUpGetOptions(timeout);
			uint messageLength=(uint)length;
			uint mqLength=0;
			byte[] message=new Byte[length];
			MQGET(_connectionHandle,_handle,messageDescriptor,getOptions,messageLength,message,ref mqLength,ref _completionCode,ref _reasonCode);
			if(_reasonCode!=ReasonCodes.MQRC_NONE)
			{
				_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
				return false;
			}
			else
			{
				messageString=GetText(message,(int)mqLength);
				messageID=messageDescriptor._MsgId;
				return true;
			}
		}
		///////////////////////////////////////////////////////////////////////////
		// browses a single message from the queue
		public string Browse(int length,bool first) {
			if(_handle==0)
			{
				_lastError="queue is not open";
				return null;
			}
			//initialise descriptor
			MQMD messageDescriptor=SetUpMessageDescriptor();
			// intialise get options
			MQGMO getOptions=SetUpBrowseOptions(first);
			uint messageLength=(uint)length;
			uint mqLength=0;
			string result=null;
			byte[] message=new Byte[length];
			MQGET(_connectionHandle,_handle,messageDescriptor,getOptions,messageLength,message,ref mqLength,ref _completionCode,ref _reasonCode);
			if(_reasonCode!=ReasonCodes.MQRC_NONE)
			{
				_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
			}
			else
			{
				result=GetText(message,(int)mqLength);
			}
			return result;
		}
		///////////////////////////////////////////////////////////////////////////
		// browses a single message and it's message id from the queue
		public bool BrowseForMessageAndID(int length,bool first,ref string messageString,ref byte[] messageID) {
			if(_handle==0)
			{
				_lastError="queue is not open";
				return false;
			}
			//initialise descriptor
			MQMD messageDescriptor=SetUpMessageDescriptor();
			// intialise get options
			MQGMO getOptions=SetUpBrowseOptions(first);
			uint messageLength=(uint)length;
			uint mqLength=0;
			byte[] message=new Byte[length];
			MQGET(_connectionHandle,_handle,messageDescriptor,getOptions,messageLength,message,ref mqLength,ref _completionCode,ref _reasonCode);
			if(_reasonCode!=ReasonCodes.MQRC_NONE)
			{
				_lastError=ReasonCodeUtils.DecodeReasonCode(_reasonCode);
				return false;
			}
			else
			{
				messageString=GetText(message,(int)mqLength);
				messageID=messageDescriptor._MsgId;
				return true;
			}
		}
		private string GetText(byte[] message,int length) {
			// Both don't work with umlauts, and stuff like that
			//return System.Text.Encoding.ASCII.GetString(message,0, length);
			//return System.Text.Encoding.UTF8.GetString(message, 0, length);
			// Both work with special characters, for obvious reasons
			//return System.Text.Encoding.GetEncoding(1252).GetString(message, 0, length);
			return System.Text.Encoding.GetEncoding("ISO-8859-1").GetString(message,0,length);
		}
		#region MQ Setup Helper Methods

		private MQGMO SetUpBrowseOptions(bool first) {
			MQGMO getOptions=new MQGMO();
			getOptions._Options=Constants.MQGMO_WAIT|Constants.MQGMO_CONVERT;
			getOptions._Options|=first?Constants.MQGMO_BROWSE_FIRST:Constants.MQGMO_BROWSE_NEXT;
			getOptions._WaitInterval=500;		// 0.5 second wait interval
			getOptions._Version=Constants.MQGMO_VERSION_2;
			getOptions._MatchOptions=Constants.MQMO_NONE;
			return getOptions;
		}

		private MQGMO SetUpGetOptions(int timeout) {
			MQGMO getOptions=new MQGMO();
			getOptions._Options=Constants.MQGMO_WAIT|Constants.MQGMO_CONVERT;
			getOptions._WaitInterval=(uint)timeout;
			getOptions._Version=Constants.MQGMO_VERSION_2;
			getOptions._MatchOptions=Constants.MQMO_NONE;
			return getOptions;
		}

		private MQGMO SetUpTransactionalGetOptions(int timeout) {
			MQGMO getOptions=new MQGMO();
			getOptions._Options=Constants.MQGMO_WAIT|Constants.MQGMO_CONVERT|Constants.MQGMO_SYNCPOINT;
			getOptions._WaitInterval=(uint)timeout;
			getOptions._Version=Constants.MQGMO_VERSION_2;
			getOptions._MatchOptions=Constants.MQMO_NONE;
			return getOptions;
		}

		private MQMD SetUpMessageDescriptor() {
			MQMD messageDescriptor=new MQMD();
			messageDescriptor._Format=MQUtils.StringToByteArray("MQSTR   ",8);
			messageDescriptor._Encoding=Constants.MQENC_NATIVE;
			messageDescriptor._CodedCharSetId=Constants.MQCCSI_ISO8859;
			return messageDescriptor;
		}



		#endregion
	}
}